package connhttp

import (
	"encoding/json"
	"errors"
	"fmt"
	"git.sr.ht/~mil/transito/connmobroute"
	"git.sr.ht/~mil/transito/utilfuncs"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"
)

type NominatimAPIResult struct {
	DisplayName string `json:"display_name"`
	Name        string `json:"name"`
	Lat         string `json:"lat"`
	Lon         string `json:"lon"`
}

func FetchNominatim(searchQuery, sourcesCSV string) (*[]NominatimAPIResult, error) {
	var (
		nominatimEndpoint = "https://nominatim.openstreetmap.org/search"
		results           []NominatimAPIResult
		latA              float64 = -90
		lonA              float64 = -180
		latB              float64 = 90
		lonB              float64 = 180
	)

	if statuses, err := connmobroute.MobrouteExecuteDBStatusMDBIDs(sourcesCSV); err != nil {
		return nil, err
	} else {
		for _, s := range statuses {
			if s.LocationBbox == nil {
				continue
			} else if parsedLatA, parsedLonA, parsedLatB, parsedLonB, err := parseBboxCoords(*s.LocationBbox); err != nil {
				return nil, err
			} else if latA == -90 {
				latA = parsedLatA
				lonA = parsedLonA
				latB = parsedLatB
				lonB = parsedLonB
			} else {
				latA = utilfuncs.MinFloat64(latA, parsedLatA)
				lonA = utilfuncs.MinFloat64(lonA, parsedLonA)
				latB = utilfuncs.MaxFloat64(latB, parsedLatB)
				lonB = utilfuncs.MaxFloat64(lonB, parsedLonB)
			}
		}
	}

	if parsedUrl, err := url.Parse(nominatimEndpoint); err != nil {
		return nil, err
	} else {
		params := url.Values{}
		params.Add("q", searchQuery)
		params.Add("bounded", "1")
		params.Add("viewbox", constructViewbox(latA, lonA, latB, lonB))
		params.Add("format", "json")
		parsedUrl.RawQuery = params.Encode()
		if resp, err := http.Get(parsedUrl.String()); err != nil {
			return nil, err
		} else if resp.StatusCode != http.StatusOK {
			return nil, fmt.Errorf("Errorcode %d from Nominatim", resp.StatusCode)
		} else if body, err := io.ReadAll(resp.Body); err != nil {
			return nil, err
		} else if err := json.Unmarshal(body, &results); err != nil {
			return nil, err
		} else {
			return &results, nil
		}
	}
}

func constructViewbox(latA, lonA, latB, lonB float64) string {
	return strings.Join(
		[]string{
			strconv.FormatFloat(lonA, 'f', -1, 64),
			strconv.FormatFloat(latA, 'f', -1, 64),
			strconv.FormatFloat(lonB, 'f', -1, 64),
			strconv.FormatFloat(latB, 'f', -1, 64),
		},
		",",
	)
}

func parseBboxCoords(bboxMobsqlString string) (float64, float64, float64, float64, error) {
	// TODO: this is a bit hacky.. really mobsql should provide bbox
	// either as straight CSV or as seperate fields, currently
	// format is [a,b,c,d] so we strip out [] since rest can be simply parsed
	bboxMobsqlString = strings.Replace(bboxMobsqlString, "[", "", -1)
	bboxMobsqlString = strings.Replace(bboxMobsqlString, "]", "", -1)
	components := strings.Split(bboxMobsqlString, ",")

	if len(components) < 4 {
		return 0, 0, 0, 0, errors.New(fmt.Sprintf("Bbox format invalid?: %s", bboxMobsqlString))
	} else if a, err := strconv.ParseFloat(components[0], 64); err != nil {
		return 0, 0, 0, 0, err
	} else if b, err := strconv.ParseFloat(components[1], 64); err != nil {
		return 0, 0, 0, 0, err
	} else if c, err := strconv.ParseFloat(components[2], 64); err != nil {
		return 0, 0, 0, 0, err
	} else if d, err := strconv.ParseFloat(components[3], 64); err != nil {
		return 0, 0, 0, 0, err
	} else {
		return a, b, c, d, nil
	}
}
